#import "PostController.h"


#include <sys/socket.h>
#include <unistd.h>

#include <CFNetwork/CFNetwork.h>

#pragma mark * Utilities

//  CFStream bound pair generates weird log message
//  CFStream bound pair crashers

static void CFStreamCreateBoundPairCompat(
    CFAllocatorRef      alloc, 
    CFReadStreamRef *   readStreamPtr, 
    CFWriteStreamRef *  writeStreamPtr, 
    CFIndex             transferBufferSize
)
    // This is a drop-in replacement for CFStreamCreateBoundPair that is necessary 
    // because the bound pairs are broken on iPhone OS up-to-and-including 
    // version 3.0.1 <rdar://problem/7027394> <rdar://problem/7027406>.  It 
    // emulates a bound pair by creating a pair of UNIX domain sockets and wrapper 
    // each end in a CFSocketStream.  This won't give great performance, but 
    // it doesn't crash!
{
    #pragma unused(transferBufferSize)
    int                 err;
    Boolean             success;
    CFReadStreamRef     readStream;
    CFWriteStreamRef    writeStream;
    int                 fds[2];
    
    assert(readStreamPtr != NULL);
    assert(writeStreamPtr != NULL);
    
    readStream = NULL;
    writeStream = NULL;
    
    // Create the UNIX domain socket pair.
    
    err = socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
    if (err == 0) {
        CFStreamCreatePairWithSocket(alloc, fds[0], &readStream,  NULL);
        CFStreamCreatePairWithSocket(alloc, fds[1], NULL, &writeStream);
        
        // If we failed to create one of the streams, ignore them both.
        
        if ( (readStream == NULL) || (writeStream == NULL) ) {
            if (readStream != NULL) {
                CFRelease(readStream);
                readStream = NULL;
            }
            if (writeStream != NULL) {
                CFRelease(writeStream);
                writeStream = NULL;
            }
        }
        assert( (readStream == NULL) == (writeStream == NULL) );
        
        // Make sure that the sockets get closed (by us in the case of an error, 
        // or by the stream if we managed to create them successfull).
        
        if (readStream == NULL) {
            err = close(fds[0]);
            assert(err == 0);
            err = close(fds[1]);
            assert(err == 0);
        } else {
            success = CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
            assert(success);
            success = CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
            assert(success);
        }
    }
    
    *readStreamPtr = readStream;
    *writeStreamPtr = writeStream;
}

// A category on NSStream that provides a nice, Objective-C friendly way to create 
// bound pairs of streams.

@interface NSStream (BoundPairAdditions)
+ (void)createBoundInputStream:(NSInputStream **)inputStreamPtr outputStream:(NSOutputStream **)outputStreamPtr bufferSize:(NSUInteger)bufferSize;
@end

@implementation NSStream (BoundPairAdditions)

+ (void)createBoundInputStream:(NSInputStream **)inputStreamPtr outputStream:(NSOutputStream **)outputStreamPtr bufferSize:(NSUInteger)bufferSize
{
    CFReadStreamRef     readStream;
    CFWriteStreamRef    writeStream;

    assert( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) );

    readStream = NULL;
    writeStream = NULL;

    if (YES) {
        CFStreamCreateBoundPairCompat(
            NULL, 
            ((inputStreamPtr  != nil) ? &readStream : NULL),
            ((outputStreamPtr != nil) ? &writeStream : NULL), 
            (CFIndex) bufferSize
        );
    } else {
        CFStreamCreateBoundPair(
            NULL, 
            ((inputStreamPtr  != nil) ? &readStream : NULL),
            ((outputStreamPtr != nil) ? &writeStream : NULL), 
            (CFIndex) bufferSize
        );
    }
    
    if (inputStreamPtr != NULL) {
        *inputStreamPtr  = [NSMakeCollectable(readStream) autorelease];
    }
    if (outputStreamPtr != NULL) {
        *outputStreamPtr = [NSMakeCollectable(writeStream) autorelease];
    }
}

@end
        
#pragma mark * PostController

enum {
    kPostBufferSize = 32768
};

@interface PostController ()

// Properties that don't need to be seen by the outside world.

@property (nonatomic, retain)   NSURLConnection * connection;
@property (nonatomic, copy)     NSData *          bodyPrefixData;
@property (nonatomic, retain)   NSInputStream *   fileStream;
@property (nonatomic, copy)     NSData *          bodySuffixData;
@property (nonatomic, retain)   NSOutputStream *  producerStream;
@property (nonatomic, retain)   NSInputStream *   consumerStream;
@property (nonatomic, assign)   const uint8_t *   buffer;
@property (nonatomic, assign)   uint8_t *         bufferOnHeap;
@property (nonatomic, assign)   size_t            bufferOffset;
@property (nonatomic, assign)   size_t            bufferLimit;
@property (nonatomic, assign)   NSData *		  imageData;
@property (nonatomic, retain)   NSURL *			  postURL;
@property (nonatomic, retain)   NSString *userName;
@property (nonatomic, retain)   NSString *passWord; 
@property (nonatomic, assign)   id<SenderViewDelegate> delegate;

@end

@implementation PostController

+ (void)releaseObj:(id)obj
    // +++ See comment in -_stopSendWithStatus:.
{
    [obj release];
}

#pragma mark * Status management

// These methods are used by the core transfer code to update the UI.

- (void)_sendDidStart
{
    [[NetImagerAppDelegate sharedAppDelegate] didStartNetworking];
}

- (void)_sendDidStopWithStatus:(NSString *)statusString
{
	if(self.delegate != nil) {
		[self.delegate doneActivityViewDelegate];				
	}
    [[NetImagerAppDelegate sharedAppDelegate] didStopNetworking];
	if(statusString != nil && [statusString length] > 0) 
	{
		if(self.delegate != nil) {
			[self.delegate didAlertViewDelegate:@"Notice" message:statusString];				
		}
	}
	
}

#pragma mark * Core transfer code

// This is the code that actually does the networking.

@synthesize connection      = _connection;
@synthesize bodyPrefixData  = _bodyPrefixData;
@synthesize fileStream      = _fileStream;
@synthesize bodySuffixData  = _bodySuffixData;
@synthesize producerStream  = _producerStream;
@synthesize consumerStream  = _consumerStream;
@synthesize buffer          = _buffer;
@synthesize bufferOnHeap    = _bufferOnHeap;
@synthesize bufferOffset    = _bufferOffset;
@synthesize bufferLimit     = _bufferLimit;
@synthesize imageData		= _imageData;
@synthesize postURL			= _postURL;
@synthesize userName		= _userName;
@synthesize passWord		= _passWord;
@synthesize delegate		= _delegate;

- (NSString *)_generateFileName
{
	srandom(time(NULL));
    NSString *result = [NSString stringWithFormat:@"%u.png", random()];
    return result;
}

- (NSString *)_generateBoundaryString
{
    CFUUIDRef       uuid;
    CFStringRef     uuidStr;
    NSString *      result;
    
    uuid = CFUUIDCreate(NULL);
    assert(uuid != NULL);
    
    uuidStr = CFUUIDCreateString(NULL, uuid);
    assert(uuidStr != NULL);
    
    result = [NSString stringWithFormat:@"Boundary-%@", uuidStr];
    
    CFRelease(uuidStr);
    CFRelease(uuid);
    
    return result;
}

- (void)_startSend
{
    NSURL *                 url;
    NSMutableURLRequest *   request;
    NSString *              boundaryStr;
    NSString *              contentType;
    NSString *              bodyPrefixStr;
    NSString *              bodySuffixStr;
    NSNumber *              fileLengthNum;
    unsigned long long      bodyLength;

    url = self.postURL;

    // If the URL is bogus, let the user know.  Otherwise kick off the connection.

    if ( url != nil){
        // Determine the MIME type of the file.
        contentType = @"image/png";
		
        // Calculate the multipart/form-data body. 
        
        boundaryStr = [self _generateBoundaryString];
        assert(boundaryStr != nil);
        bodyPrefixStr = [NSString stringWithFormat:
            @
            // empty preamble
            "\r\n"
            "--%@\r\n"
            "Content-Disposition: form-data; name=\"userfile\"; filename=\"%@\"\r\n"
            "Content-Type: %@\r\n"
            "\r\n",
            boundaryStr,
            [self _generateFileName],       // +++ very broken for non-ASCII
            contentType
        ];
        assert(bodyPrefixStr != nil);

        bodySuffixStr = [NSString stringWithFormat:
            @
            "\r\n"
            "--%@\r\n"
            "Content-Disposition: form-data; name=\"username\"\r\n"
            "\r\n"
            "%@\r\n"
            "--%@--\r\n" 
            "\r\n"
			 "\r\n"
			 "--%@\r\n"
			 "Content-Disposition: form-data; name=\"password\"\r\n"
			 "\r\n"
			 "%@\r\n"
			 "--%@--\r\n" 
			 "\r\n"
						 //empty epilogue
            ,
            boundaryStr, 
			self.userName,
            boundaryStr,
			 boundaryStr, 
			 self.passWord,
			 boundaryStr
						 
        ];
        assert(bodySuffixStr != nil);

        self.bodyPrefixData = [bodyPrefixStr dataUsingEncoding:NSASCIIStringEncoding];
        assert(self.bodyPrefixData != nil);
        self.bodySuffixData = [bodySuffixStr dataUsingEncoding:NSASCIIStringEncoding];
        assert(self.bodySuffixData != nil);

        fileLengthNum = [NSNumber numberWithInt:[self.imageData length]];

        bodyLength =
              (unsigned long long) [self.bodyPrefixData length]
            + [fileLengthNum unsignedLongLongValue]
            + (unsigned long long) [self.bodySuffixData length];
        
        // Open a stream for the file we're going to send.  We open this stream 
        // straight away because there's no need to delay.
        
        self.fileStream = [NSInputStream inputStreamWithData:self.imageData];
        assert(self.fileStream != nil);
        
        [self.fileStream open];
        
        // Open producer/consumer streams.  We open the producerStream straight 
        // away.  We leave the consumerStream alone; NSURLConnection will deal 
        // with it.
        
        [NSStream createBoundInputStream:&self->_consumerStream outputStream:&self->_producerStream bufferSize:32768];
        [self->_consumerStream retain];
        [self->_producerStream retain];
        assert(self.consumerStream != nil);
        assert(self.producerStream != nil);
        
        self.producerStream.delegate = self;
        [self.producerStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [self.producerStream open];
        
        // Set up our state to send the body prefix first.
        
        self.buffer      = [self.bodyPrefixData bytes];
        self.bufferLimit = [self.bodyPrefixData length];
        
        // Open a connection for the URL, configured to POST the file.

        request = [NSMutableURLRequest requestWithURL:url];
        assert(request != nil);
        
        [request setHTTPMethod:@"POST"];
        [request setHTTPBodyStream:self.consumerStream];
        
        [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", boundaryStr] forHTTPHeaderField:@"Content-Type"];
        [request setValue:[NSString stringWithFormat:@"%llu", bodyLength] forHTTPHeaderField:@"Content-Length"];
        
        self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
        assert(self.connection != nil);
        
        // Tell the UI we're sending.
        
        [self _sendDidStart];
    }
}

- (void)_stopSendWithStatus:(NSString *)statusString
{
    if (self.bufferOnHeap) {
        free(self.bufferOnHeap);
        self.bufferOnHeap = NULL;
    }
    self.buffer = NULL;
    self.bufferOffset = 0;
    self.bufferLimit  = 0;
    if (self.connection != nil) {
        [self.connection cancel];
		[self.connection release]; // for memory leak
        _connection = nil;
    }
	if(self.bodyPrefixData != nil)
	{
		[self.bodyPrefixData release];
		_bodyPrefixData = nil;
	}
    if (self.producerStream != nil) {
        self.producerStream.delegate = nil;
        [self.producerStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [self.producerStream close];
		[self.producerStream release];
        _producerStream = nil;
    }
    if(self.consumerStream != nil)
	{
		self.consumerStream.delegate = nil;
		[self.consumerStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
		[self.consumerStream close];
		[self.consumerStream release];
		_consumerStream = nil;
	}
    if (self.fileStream != nil) {
		self.fileStream.delegate = nil;
		[self.fileStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [self.fileStream close];
		[self.fileStream release];
		_fileStream = nil;
    }

    [self _sendDidStopWithStatus:statusString];
}

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
    // An NSStream delegate callback that's called when events happen on our 
    // network stream.
{
    #pragma unused(aStream)
    assert(aStream == self.producerStream);

    switch (eventCode) {
        case NSStreamEventOpenCompleted: {
            // NSLog(@"producer stream opened");
        } break;
        case NSStreamEventHasBytesAvailable: {
            assert(NO);     // should never happen for the output stream
        } break;
        case NSStreamEventHasSpaceAvailable: {
            // Check to see if we've run off the end of our buffer.  If we have, 
            // work out the next buffer of data to send.
            
            if (self.bufferOffset == self.bufferLimit) {

                // See if we're transitioning from the prefix to the file data.
                // If so, allocate a file buffer.
                
                if (self.bodyPrefixData != nil) {
                    self.bodyPrefixData = nil;

                    assert(self.bufferOnHeap == NULL);
                    self.bufferOnHeap = malloc(kPostBufferSize);
                    assert(self.bufferOnHeap != NULL);
                    self.buffer = self.bufferOnHeap;
                    
                    self.bufferOffset = 0;
                    self.bufferLimit  = 0;
                }
                
                // If we still have file data to send, read the next chunk. 
                
                if (self.fileStream != nil) {
                    NSInteger   bytesRead;
                    
                    bytesRead = [self.fileStream read:self.bufferOnHeap maxLength:kPostBufferSize];
                    
                    if (bytesRead == -1) {
                        [self _stopSendWithStatus:@"File read error"];
                    } else if (bytesRead != 0) {
                        self.bufferOffset = 0;
                        self.bufferLimit  = bytesRead;
                    } else {
                        // If we hit the end of the file, transition to sending the 
                        // suffix.

                        [self.fileStream close];
                        self.fileStream = nil;
                        
                        assert(self.bufferOnHeap != NULL);
                        free(self.bufferOnHeap);
                        self.bufferOnHeap = NULL;
                        self.buffer       = [self.bodySuffixData bytes];

                        self.bufferOffset = 0;
                        self.bufferLimit  = [self.bodySuffixData length];
                    }
                }
                
                // If we've failed to produce any more data, we close the stream 
                // to indicate to NSURLConnection that we're all done.  We only do 
                // this if producerStream is still valid to avoid running it in the 
                // file read error case.
                
                if ( (self.bufferOffset == self.bufferLimit) && (self.producerStream != nil) ) {
                    // We set our delegate callback to nil because we don't want to 
                    // be called anymore for this stream.  However, we can't 
                    // remove the stream from the runloop (doing so prevents the 
                    // URL from ever completing) and nor can we nil out our 
                    // stream reference (that causes all sorts of wacky crashes). 
                    //
                    // +++ Need bug numbers for these problems.
                    self.producerStream.delegate = nil;
                    // [self.producerStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
                    [self.producerStream close];
                    // self.producerStream = nil;
                }
            }
            
            // Send the next chunk of data in our buffer.
            
            if (self.bufferOffset != self.bufferLimit) {
                NSInteger   bytesWritten;
                bytesWritten = [self.producerStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
                if (bytesWritten <= 0) {
                    [self _stopSendWithStatus:@"Network write error"];
                } else {
                    self.bufferOffset += bytesWritten;
                }
            }
        } break;
        case NSStreamEventErrorOccurred: {
            NSLog(@"producer stream error %@", [aStream streamError]);
            [self _stopSendWithStatus:@"Stream open error"];
        } break;
        case NSStreamEventEndEncountered: {
            assert(NO);     // should never happen for the output stream
        } break;
        default: {
            assert(NO);
        } break;
    }
}

- (void)connection:(NSURLConnection *)theConnection didReceiveResponse:(NSURLResponse *)response
    // A delegate method called by the NSURLConnection when the request/response 
    // exchange is complete.  We look at the response to check that the HTTP 
    // status code is 2xx.  If it isn't, we fail right now.
{
    #pragma unused(theConnection)
    NSHTTPURLResponse * httpResponse;

    assert(theConnection == self.connection);
    
    httpResponse = (NSHTTPURLResponse *) response;
    assert( [httpResponse isKindOfClass:[NSHTTPURLResponse class]] );
    
    if ((httpResponse.statusCode / 100) != 2) {
        [self _stopSendWithStatus:[NSString stringWithFormat:@"HTTP error %zd", (ssize_t) httpResponse.statusCode]];
    } else {
		if(self.delegate != nil) {
			[self.delegate didAlertViewDelegate:@"Notice" message:@"Upload Successfully!"];				
		}
        NSLog(@"Response OK.");
    }    
}


- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)data
    // A delegate method called by the NSURLConnection as data arrives.  The 
    // response data for a POST is only for useful for debugging purposes, 
    // so we just drop it on the floor.
{
    #pragma unused(theConnection)
    #pragma unused(data)

    assert(theConnection == self.connection);

    // do nothing
}

- (void)connection:(NSURLConnection *)theConnection didFailWithError:(NSError *)error
    // A delegate method called by the NSURLConnection if the connection fails. 
    // We shut down the connection and display the failure.  Production quality code 
    // would either display or log the actual error.
{
    #pragma unused(theConnection)
    #pragma unused(error)
    assert(theConnection == self.connection);
    
    [self _stopSendWithStatus:@"Connection failed"];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)theConnection
    // A delegate method called by the NSURLConnection when the connection has been 
    // done successfully.  We shut down the connection with a nil status, which 
    // causes the image to be displayed.
{
    #pragma unused(theConnection)
    assert(theConnection == self.connection);
    
    [self _stopSendWithStatus:nil];
}

#pragma mark * Actions

- (void)sendAction:(id)sender userName:(NSString *)name
		  passWord:(NSString*)pass
		   postURL:(NSString *)postUrl
		 imagePost:(UIImage *)image
{
	
	if(sender != nil && [sender conformsToProtocol:@protocol(SenderViewDelegate)])
	{
		self.delegate = sender;
		[self.delegate appearActivityViewDelegate];
	}
	
 	if(image != nil) {
		self.imageData = UIImagePNGRepresentation(image);
		assert(self.imageData != nil);
	}
    
    self.postURL = [NSURL URLWithString:postUrl];
	self.userName = name;
	self.passWord = pass;
	
    if ( self.postURL!=nil && image !=nil ) {
        [self _startSend];
    }
}

- (void)cancelAction:(id)sender;
{
    #pragma unused(sender)
    [self _stopSendWithStatus:@"Cancelled"];
}


- (void)dealloc
{
	[self _stopSendWithStatus:@""];
	[self.userName release];
	[self.passWord release];
	[self.postURL release];
    [super dealloc];
}

@end
